﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SimpleBuffer
{
    public abstract class AbstractBuffer<T> : IBuffer<T>
    {
        private T[] array;
        private int readerIndex;
        private int writerIndex;

        private readonly int defaultIncrement = 256;

        public AbstractBuffer() : this(256)
        {
        }

        public AbstractBuffer(int capacity)
        {
            array = new T[capacity];
            AutoIncrement = true;
            IncrementSize = defaultIncrement;
        }

        public AbstractBuffer(T[] buffer) : this()
        {
            Write(buffer);
        }

        public virtual T this[int index] { get => Get(index); set => Set(index, value); }

        public virtual void Set(int index, T value)
        {
            if (index < 0 || index > ReadableLength)
                throw new IndexOutOfRangeException("index out of array range.");
            array[(readerIndex + index) % Capacity] = value;
        }

        public virtual T Get(int index)
        {
            if (index < 0 || index > ReadableLength)
                throw new IndexOutOfRangeException("index out of array range.");
            return array[(readerIndex + index) % Capacity];
        }

        public virtual int ReaderIndex => readerIndex;

        public virtual int WriterIndex => writerIndex;

        public virtual int Capacity => array.Length;

        public int Length => ReadableLength;

        /// <summary>
        /// 可读长度
        /// WriterIndex等于ReaderIndex时可读长度为0
        /// </summary>
        public virtual int ReadableLength => (writerIndex - readerIndex + Capacity) % Capacity;

        public virtual int WritableLength => Capacity - ReadableLength - 1;

        public bool AutoIncrement { get; set; }
        public int IncrementSize { get; set; }

        public virtual T[] Array => array;

        public virtual T[] Read(int length)
        {
            var buffer = Get(0, length);
            readerIndex = (readerIndex + length) % Capacity;
            return buffer;
        }

        public virtual void Write(T[] value)
        {
            if (value == null)
                throw new ArgumentNullException("parameter value is null.");
            if (!AutoIncrement && value.Length > WritableLength)
                throw new ArgumentException("write length greater than writable length.");

            if (value.Length == 0)
                return;

            if (value.Length > WritableLength)
            {
                var increment = IncrementSize > 0 ? IncrementSize : defaultIncrement;
                int count = (value.Length - WritableLength) / increment + 1;
                T[] buffer = Read(ReadableLength);
                array = new T[Capacity + count * increment];
                readerIndex = 0;
                writerIndex = 0;
                Write(buffer);
            }
            int startIndex = writerIndex;
            int tail = Capacity - startIndex;
            if (readerIndex > writerIndex || tail > value.Length)
            {
                System.Array.Copy(value, 0, array, startIndex, value.Length);
            }
            else
            {
                System.Array.Copy(value, 0, array, startIndex, tail);
                System.Array.Copy(value, tail, array, 0, value.Length - tail);
            }

            writerIndex = (writerIndex + value.Length) % Capacity;
        }

        /// <summary>
        /// 读取内容
        /// </summary>
        /// <param name="index">相对ReaderIndex的偏移量</param>
        /// <param name="length">读取长度</param>
        /// <returns></returns>
        public virtual T[] Get(int index, int length)
        {
            if (index < 0 || index > ReadableLength)
                throw new IndexOutOfRangeException("index out of array range.");
            if (length < 0)
                throw new ArgumentOutOfRangeException("read length less than zero.");
            if (length + index > ReadableLength)
                throw new ArgumentOutOfRangeException("read length greater than readable length.");

            int startIndex = (readerIndex + index) % Capacity;

            T[] buffer = new T[length];
            int tail = Capacity - startIndex;
            if (writerIndex >= readerIndex || tail > length)
            {
                System.Array.Copy(array, startIndex, buffer, 0, length);
            }
            else
            {
                System.Array.Copy(array, startIndex, buffer, 0, tail);
                System.Array.Copy(array, 0, buffer, tail, length - tail);
            }
            return buffer;
        }

        /// <summary>
        /// 设置内容，只能设置可读部分
        /// </summary>
        /// <param name="index">相对ReaderIndex的偏移量</param>
        /// <param name="value"></param>
        public virtual void Set(int index, T[] value)
        {
            if (index < 0 || index > ReadableLength)
                throw new IndexOutOfRangeException("index out of array range.");
            if (value == null)
                throw new ArgumentNullException("parameter value is null.");
            if (value.Length + index > ReadableLength)
                throw new ArgumentOutOfRangeException("set length greater than readable length.");

            var startIndex = (readerIndex + value.Length) % Capacity;
            var tail = Capacity - startIndex;

            if (writerIndex > readerIndex || tail > value.Length)
            {
                System.Array.Copy(value, 0, array, startIndex, value.Length);
            }
            else
            {
                System.Array.Copy(value, 0, array, startIndex, tail);
                System.Array.Copy(value, tail, array, 0, value.Length - tail);
            }
        }
    }
}
